package com.bracketbird.client.gui.club.tournament.rtc;

import com.bracketbird.client.model.*;
import com.bracketbird.client.model.keys.*;
import com.bracketbird.client.model.tournament.*;
import com.bracketbird.client.service.*;
import com.bracketbird.client.service.rtc.*;
import com.bracketbird.clientcore.appcontrol.*;
import com.bracketbird.clientcore.model.*;
import com.bracketbird.clientcore.service.*;
import com.google.gwt.user.client.*;

import java.util.*;

/**
 *
 */
public class ServerEventQueue {

    private TournamentId tournamentId;

    private List<RTCEvent> jailList = new ArrayList<RTCEvent>();

    //Events that has been sent to server with succes.
    private List<RTCEvent> doneEvents = new ArrayList<RTCEvent>();

    //Events waiting to be sent to server. One is send at a time.
    private List<RTCEvent> pendingEvents = new ArrayList<RTCEvent>();
    private Command command = null;


    public ServerEventQueue(TournamentId tournamentId) {
        this.tournamentId = tournamentId;
    }

    public void start(List<RTCEvent> doneEvs) {
        handleServerEvents(doneEvs);
    }

    public void add(RTCEvent event) {
        pendingEvents.add(event);

        send();
    }

    public List<RTCEvent> getDoneEvents() {
        return doneEvents;
    }

    public TournamentId getTournamentId() {
        return tournamentId;
    }


    public void stateChanged(TournamentStateChangedEvent event) {

    }


    public void stateChanged(LevelStateEvent event) {

    }


    private void send() {
        if (pendingEvents.isEmpty() || !jailList.isEmpty()) {
            return;
        }
        if (command == null) {
            System.out.println("jailist is EMPTY.....");
            command = new Command() {
                public void execute() {
                    try {

                        final RTCEvent event = pendingEvents.get(pendingEvents.size() - 1);


                        if (event.getType().intValue() == RTCEventType.addLevel.value() || event.getType().intValue() == RTCEventType.addTeam.value()) {
                            jailList.add(event);
                            event.setLocalId(event.getModel().getId().getLocalId());
                        }


                        event.setState(getLastState());
                        //has to be set this late - because modelId is first set when returned from server when created.
                        if (event.getModel() != null) {
                            event.setModelId(event.getModel().getId());
                        }
                        RTCRequest r = new RTCRequest(event, getLastServerLogId());
                        BBService.updateRunningTournament(new RTCAction(r), new CallBack<RTCResult>() {
                            @Override
                            public void success(RTCResult result) {
                                try {
                                    pendingEvents.remove(pendingEvents.size() - 1);
                                    bindIdAndCreatedDateToModel(result.getResponse().getEvents());
                                    handleSucces(event, result.getResponse());
                                    command = null;
                                    send();
                                }
                                catch (Exception e) {
                                    RTC.getInstance().handleFailure(e, null);
                                }
                            }

                            @Override
                            public void fail(Throwable t) {
                                RTC.getInstance().handleFailure(t, null);
                            }
                        });
                    }
                    catch (Exception e) {
                        RTC.getInstance().handleFailure(e, null);
                    }
                }
            };

            //running the command when client is passiv.
            DeferredCommand.addCommand(command);
        }
    }

    private Long getLastServerLogId() {
        return doneEvents.get(doneEvents.size() - 1).getEventLogId();
    }

    private Integer getLastState() {
        return doneEvents.get(doneEvents.size() - 1).getState();
    }

    private void handleSucces(RTCEvent event, RTCResponse response) {
        List<RTCEvent> list = response.getEvents();
        if (response.getStatus() == RTCResponse.Status.not_mergable) {
            //System.out.println("NOT MERGEABLE");
            reloadTournament(response);
        }
        else {
            if (response.getStatus().equals(RTCResponse.Status.succes)) {
                //System.out.println("SUCCES");

                if (list.size() != 1) {
                    throw new SystemException("Expected list size is 1. Received size is:" + list.size());
                }
                doneEvents.addAll(list);
            }
            else if (response.getStatus().equals(RTCResponse.Status.merged)) {
                //System.out.println("MERGED");

                if (list.size() < 2) {
                    throw new SystemException("Expected list size is >1. Received size is:" + list.size());
                }

                //when merged, the last event is already handled - but should still be put on doneEventsArray
                RTCEvent last = list.remove(list.size() - 1);
                handleServerEvents(response.getEvents());
                doneEvents.add(last);
            }
            else {
                throw new SystemException("ERROR:Status not implemented. Status:" + response.getStatus());
            }

        }
    }

    private void bindIdAndCreatedDateToModel(List<RTCEvent> events) {

        for (RTCEvent event : events) {

            int index = 0;
            for (RTCEvent rtcEvent : jailList) {
                if (event.getLocalId().longValue() == rtcEvent.getLocalId()) {
                    //noinspection unchecked
                    rtcEvent.getModel().setId(event.getModelId());
                    rtcEvent.getModel().setCreatedDate(event.getCreatedDate());
                    jailList.remove(index);
                    return;
                }
                index++;
            }

        }


    }

    private void reloadTournament(RTCResponse response) {
        //add new events to existing events and reload all
        List<RTCEvent> allEvents = getDoneEvents();
        allEvents.addAll(response.getEvents());

        RTC.getInstance().reLoadTournament(allEvents);
    }


    private void handleServerEvents(List<RTCEvent> events) {
        for (RTCEvent e : events) {

            int type = e.getType();

            if (e.getDeleted()) {
                //ignore
            }
            //TEAM
            else if (type == RTCEventType.addTeam.value()) {
                PlayingTeam pt = new PlayingTeam();
                pt.setId((PlayingTeamId) e.getModelId());
                pt.setEventLogId(e.getEventLogId());
                RTC.getInstance().addTeam(pt, false);
            }
            else if (type == RTCEventType.deleteTeam.value()) {
                PlayingTeam pt = new PlayingTeam();
                pt.setId((PlayingTeamId) e.getModelId());
                pt.setEventLogId(e.getEventLogId());
                RTC.getInstance().deleteTeam(pt, false);
            }
            else if (type == RTCEventType.updateTeam.value()) {
                PlayingTeam pt = new PlayingTeam();
                pt.setId((PlayingTeamId) e.getModelId());
                pt.setEventLogId(e.getEventLogId());
                RTC.getInstance().updateTeam(pt, e.getPlayingTeamVO(), false);
            }
            //LEVEL
            else if (type == RTCEventType.addLevel.value()) {
                TournamentLevelVO vo = e.getTournamentLevelVO();
                TournamentLevel tl = TournamentLevelFac.create(RTC.getInstance().getTournament(), vo.getTournamentLevelType());
                tl.setId((TournamentLevelId) e.getModelId());
                tl.setEventLogId(e.getEventLogId());
                RTC.getInstance().addLevel(tl, false);
            }
            else if (type == RTCEventType.updateLevel.value()) {
                TournamentLevelVO vo = e.getTournamentLevelVO();
                TournamentLevel tl = TournamentLevelFac.create(RTC.getInstance().getTournament(), vo.getTournamentLevelType());
                tl.setId((TournamentLevelId) e.getModelId());
                tl.setEventLogId(e.getEventLogId());
                LevelSettings ls = new LevelSettings();
                ls.update(vo);

                RTC.getInstance().updateLevelSettings(tl, ls, false);
            }
            else if (type == RTCEventType.finishedLevel.value()) {
                //doesnt matter what type - only uses id  to find relevant tournament level.
                TournamentLevel tl = new Cup();
                tl.setId((TournamentLevelId) e.getModelId());
                tl.setEventLogId(e.getEventLogId());

                RTC.getInstance().finished(tl, e.getLevelFinishedVO(), false);
            }

            else if (type == RTCEventType.deleteLevel.value()) {
                //doesnt matter what type - only uses id  to find relevant tournament level.
                TournamentLevel tl = new Cup();
                tl.setId((TournamentLevelId) e.getModelId());
                tl.setEventLogId(e.getEventLogId());
                RTC.getInstance().deleteLevel(tl, false);
            }
            else if (type == RTCEventType.layoutMatches.value()) {
                //doesnt matter what type - only uses id to find relevant tournament level.
                TournamentLevel tl = new Cup();
                tl.setId((TournamentLevelId) e.getModelId());
                tl.setEventLogId(e.getEventLogId());
                RTC.getInstance().layoutMatches(tl, false);
            }
            else if (type == RTCEventType.updateMatch.value()) {
                //doesnt matter what type - only uses id to find relevant tournament level.
                TournamentLevel tl = new Cup();
                tl.setId((TournamentLevelId) e.getModelId());
                tl.setEventLogId(e.getEventLogId());
                RTC.getInstance().updateMatch(tl, e.getMatchVO(), false);

            }
            else if (type == RTCEventType.createTournament.value()) {
                //ignore

            }
            else {
                throw new SystemException("Event type not implemented: Name:" + e.getName() + "Value:" + e.getType());
            }

            getDoneEvents().add(e);

        }
    }

    public void cancelAll() {
        pendingEvents = new ArrayList<RTCEvent>();
    }


}
